#include <iostream>
#include <opencv2/opencv.hpp>

static int mouse_x = 0, mouse_y = 0;

static inline void rgb2yuv(int r, int g, int b, int &y, int &u, int &v)
{
	y = (306*r + 601*g + 117*b)  >> 10;
	u = ((-172*r - 340*g + 512*b) >> 10)  + 128;
	v = ((512*r - 429*g - 83*b) >> 10) + 128;
	y = y < 0 ? 0 : y;
	u = u < 0 ? 0 : u;
	v = v < 0 ? 0 : v;
	y = y > 255 ? 255 : y;
	u = u > 255 ? 255 : u;
	v = v > 255 ? 255 : v;
}

// From: http://www.cs.rit.edu/~ncs/color/t_convert.html
// r,g,b values are from 0 to 1
// h = [0,360], s = [0,1], v = [0,1]
//		if s == 0, then h = -1 (undefined)
static inline void rgb2hsv(float r, float g, float b, float &h, float &s, float &v)
{
	float min, max, delta;

	min = r < g ? r : g;
	min = min < b ? min : b;
	max = r > g ? r : g;
	max = max > b ? max : b;
	delta = max - min;

	// v
	v = max;

	// s
	if (max) {
		s = delta / max; // (v - min) / v
	} else { // r = g = b = 0
		s = 0;
		h = -1;
		return;
	}

	// h
	if (!delta) {
		h = 0;
		return;
	}
	if (r == max) {
		h = (g - b) / delta;
	} else if (g == max) {
		h = 2 + (b - r) / delta;
	} else {
		h = 4 + (r - g) / delta;
	}
	h *= 60;
	if (h < 0) {
		h += 360;
	}
}

static void printRGB(void *data, int x1, int y1, int x2, int y2)
{
	cv::Mat *mat;
	unsigned char *image;
	unsigned char *addr1, *addr2;
	int width;
	int r, g, b;
	int r_total = 0, g_total = 0, b_total = 0;
	int r_min = 255, r_max = 0, g_min = 255, g_max = 0, b_min = 255, b_max = 0;
	int y, u, v;
	int y_total = 0, u_total = 0, v_total = 0;
	int y_min = 255, y_max = 0, u_min = 255, u_max = 0, v_min = 255, v_max = 0;
	float hsv_h, hsv_s, hsv_v;
	float hsv_h_total = 0.0f, hsv_s_total = 0.0f, hsv_v_total = 0;
	float hsv_h_min = 360.0f, hsv_h_max = 0.0f, hsv_s_min = 1.0f, hsv_s_max = 0.0f, hsv_v_min = 1.0f, hsv_v_max = 0.0f;
	int count = 0;

	mat = (cv::Mat *)data;
	image = mat->data;
	width = mat->cols;
	for (int i = y1; i <= y2; ++i) {
		addr1 = image + width * 3 * i;
		for (int j = x1; j <= x2; ++j) {
			addr2 = addr1 + 3 * j;
			b = *(addr2++);
			g = *(addr2++);
			r = *(addr2);

			b_total += b;
			g_total += g;
			r_total += r;
			if (r < r_min) r_min = r;
			if (r > r_max) r_max = r;
			if (g < g_min) g_min = g;
			if (g > g_max) g_max = g;
			if (b < b_min) b_min = b;
			if (b > b_max) b_max = b;

			rgb2yuv(r, g, b, y, u, v);
			y_total += y;
			u_total += u;
			v_total += v;
			if (y < y_min) y_min = y;
			if (y > y_max) y_max = y;
			if (u < u_min) u_min = u;
			if (u > u_max) u_max = u;
			if (v < v_min) v_min = v;
			if (v > v_max) v_max = v;

			rgb2hsv(r / 255.0f, g / 255.0f, b / 255.0f, hsv_h, hsv_s, hsv_v);
			hsv_h_total += hsv_h;
			hsv_s_total += hsv_s;
			hsv_v_total += hsv_v;
			if (hsv_h < hsv_h_min) hsv_h_min = hsv_h;
			if (hsv_h > hsv_h_max) hsv_h_max = hsv_h;
			if (hsv_s < hsv_s_min) hsv_s_min = hsv_s;
			if (hsv_s > hsv_s_max) hsv_s_max = hsv_s;
			if (hsv_v < hsv_v_min) hsv_v_min = hsv_v;
			if (hsv_v > hsv_v_max) hsv_v_max = hsv_v;

			++count;
		}
	}

	if (count != 0) {
		r = r_total / count;
		g = g_total / count;
		b = b_total / count;
		y = y_total / count;
		u = u_total / count;
		v = v_total / count;
		hsv_h = hsv_h_total / count;
		hsv_s = hsv_s_total / count;
		hsv_v = hsv_v_total / count;
	}
	//std::cout << "rgb_average: (" << r << ", " << g << ", " << b << ")" << std::endl;
	//std::cout << "rgb_range: (" << r_min << ":" << r_max << ", "
	//	<< g_min << ":" << g_max << ", "
	//	<< b_min << ":" << b_max << ")" << std::endl;
	std::cout << "yuv_average: (" << y << ", " << u << ", " << v << ")" << std::endl;
	std::cout << "yuv_range: (" << y_min << ":" << y_max << ", "
		<< u_min << ":" << u_max << ", "
		<< v_min << ":" << v_max << ")" << std::endl;
	//std::cout << "hsv_average: (" << hsv_h << ", " << hsv_s << ", " << hsv_v << ")" << std::endl;
	//std::cout << "hsv_range: (" << hsv_h_min << ":" << hsv_h_max << ", "
	//	<< hsv_s_min << ":" << hsv_s_max << ", "
	//	<< hsv_v_min << ":" << hsv_v_max << ")" << std::endl;
	std::cout << std::endl;
}

static void onMouse (int event, int x, int y, int, void *data)
{
	static int x_start, y_start, x_end, y_end;

	switch (event) {
	case CV_EVENT_LBUTTONDOWN:
		x_start = x;
		y_start = y;
		break;
		
	case CV_EVENT_LBUTTONUP:
		x_end = x;
		y_end = y;
		if (x_start <= x_end && y_start <= y_end) {
			printRGB(data, x_start, y_start, x_end, y_end);
		}
		break;

	case CV_EVENT_MOUSEMOVE:
		mouse_x = x;
		mouse_y = y;
		break;

	default:
		break;
	}
}

int main(int argc, char **argv)
{
	cv::VideoCapture capture(0);
	cv::Mat mat;
	char text[100];
	int ret;

	if (!capture.isOpened()) {
		return -1;
	}

	cv::namedWindow("window");
	cv::setMouseCallback("window", onMouse, &mat);
	while (true) {
		capture >> mat;
		sprintf(text, "Mouse %d %d", mouse_x, mouse_y);
		cv::putText(mat, text, cvPoint(30, 30), CV_FONT_HERSHEY_SIMPLEX, 1.0, CV_RGB(255, 0, 0) );
		cv::imshow("window", mat);
		ret = cv::waitKey(300);
		if (ret == 's') {
			cv::imwrite("a.png", mat);
		} else if (ret != -1) {
			break;
		}
	}
	return 0;
}
